预测医疗费用的回归任务
导言
在此示例中,将执行探索性数据分析,包括对集合结构的初始检查,研究特征和目标变量的分布,识别遗漏,异常值和重复项,以及分析变量之间的相关性。 在下一阶段,将构建和训练回归模型,以便预测目标变量并使用适当的指标评估其工作质量。 此外,将对特征的重要性进行分析,这将确定哪些特征对模型的预测贡献最大,并且可以被认为对所研究的任务最重要。
必须安装库
您必须手动指定项目所在的路径才能转到工作目录,以及安装必要的依赖项。
在开始工作之前,我们将导入所有必要的库。
!pip install -r /user/Demo_public/biomedical/predictionmeddata/requirements.txt
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from scipy.stats import randint, uniform, loguniform
from sklearn.model_selection import train_test_split, RandomizedSearchCV, KFold
from sklearn.linear_model import Lasso
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
from sklearn.ensemble import RandomForestRegressor
from catboost import CatBoostRegressor
我们数数据,看看它们是什么。
df = pd.read_csv("insurance.csv")
df.head(10)
让我们分析一下表:
- 年龄-年龄
- 性别-性别
- BMI-体重指数
- 儿童—儿童人数
- 吸烟者-吸烟与否
- 地区-居住地区
- 费用-医疗费用
基于此表,我们有特征1到6-回归模型的输入数据,特征7-需要根据特征1-6预测的目标。
EDA分析
让我们检查表中的差距
df.info()
没有通行证。 我们在表3中看到稍后将编码用于训练模型的分类特征,其余特征是数字特征。 你也可以看到没有差距。
df.describe()
统计数据显示,人们的平均年龄为39岁,偏差为14,即从25到53不等。 还可以看出,bmi=53存在-这是最大值,大大超过临界阈值。 也许这是一个异常值。 还可以看出,一半的人的bmi超过30,这意味着大多数人患有肥胖的初始阶段,四分之一的人患有最后程度的肥胖。 最大的成本是63k常规单位(CU),这是非常出图(均值+3sigma,均值-3sigma),也许这个观察也是一个异常值
经典机器学习模型不能使用分类特征,即不是数字的特征。 它们需要编码成数字。 我们使用一个热编码技术
df_encode = pd.get_dummies(df, columns=["sex", "region", "smoker"])
df_encode = df_encode.drop('sex_female', axis=1)
df_encode = df_encode.drop('smoker_no', axis=1)
df_encode.head()
首先,让我们研究数据的分布。 让我们为标志建立直方图
colors = ['blue', 'green', 'red', 'purple', 'orange']
fig, axes = plt.subplots(nrows=4, ncols=1,figsize=(7, 7))
axes = axes.flatten()
axes[0].hist(df["age"], bins=10, color=colors[0])
axes[0].set_title('年龄')
axes[0].set_xlabel("人的年龄")
axes[0].set_ylabel("人数")
axes[1].hist(df["bmi"], bins=10, color=colors[0])
axes[1].set_title('bmi')
axes[1].set_xlabel("bmi")
axes[1].set_ylabel("人数")
axes[2].hist(df["children"], bins=6, color=colors[0])
axes[2].set_title('儿童')
axes[2].set_xlabel("儿童人数")
axes[2].set_ylabel("人数")
axes[3].hist(df["charges"], bins=10, color=colors[0])
axes[3].set_title('开支')
axes[3].set_xlabel("人民开支")
axes[3].set_ylabel("人数")
plt.subplots_adjust(hspace=0.6)
plt.tight_layout()
通过年龄,可以看出样本中的数据分布相当均匀:既有年轻(从18岁开始),也有老年人(高达64岁),但没有明显的扭曲。
BMI分布具有正态分布的外观,就像向右移动的钟形。 大多数人在25到35的范围内,这对应于超重和肥胖。 有些客户的BMI非常高(>40)。
就儿童数量而言,分布严重倾斜:大多数客户没有孩子。
从支出中可以看出,大多数人更愿意花费低于30k单位。
接下来,我们将分析一个或另一个功能如何相互依赖。 考虑特征的相关性。 我们将使用Spearman相关性,因为它不需要分布的正态性。
spearman_corr = df_encode.corr(method='spearman')
spearman_corr['charges'].sort_values(ascending=False)
根据上表,我们可以得出结论,吸烟和年龄因素与费用有很高的相关性,即如果一个人吸烟,那么由于需要花在香烟上,他有更多的费用。 同样随着年龄的增长。 根据年龄的不同,一个人有不同的费用。 您还可以了解到,儿童数量不会对支出产生很大影响(您可以看到大多数观察到的人没有孩子,因此与支出的相关性很小),以及居住地区,性别和bmi。
让我们看看分类功能如何影响支出。
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
sns.boxplot(y="region", x="charges", data=df, ax=axes[0])
axes[0].set_title("按地区划分的开支")
sns.boxplot(y="sex", x="charges", data=df, ax=axes[1])
axes[1].set_title("按性别划分的开支")
sns.boxplot(y="smoker", x="charges", data=df, ax=axes[2])
axes[2].set_title("吸烟费用")
plt.tight_layout()
plt.show()
从各地区可以看出,成本几乎没有差异。 地区分布相似,排放随处可见。
性别也没有明显的差异。
吸烟的情况是相反的。 对于非吸烟者来说,成本低于10千,而对于吸烟者来说-大约是35千。 吸烟者之间的传播也要广泛得多,这表明存在大量成本极高的病例。
plt.figure(figsize=(8,5))
sns.scatterplot(x="age", y="charges", hue="smoker", data=df, alpha=0.6)
plt.title("费用对年龄的依赖,考虑到吸烟")
plt.xlabel("年龄")
plt.ylabel("开支 ")
plt.show()
您可以看到成本随着年龄的增长而线性增加,即为老年人提供服务更昂贵。 同样清楚的是,在吸烟因素存在的情况下,有一定的系数可以改变费用对年龄的依赖性。
df['bmi_group'] = pd.cut(df['bmi'], bins=[0, 25, 30, 100],
labels=['标准', '超重', '肥胖'])
g = sns.FacetGrid(df, col="smoker", hue="bmi_group", height=5)
g.map(sns.scatterplot, "age", "charges", alpha=0.6).add_legend()
plt.show()
从上面的图表可以得出以下结论:
对于非吸烟者,费用随着年龄的增长而增加,但通常保持在中等限度内。 即使肥胖,成本通常不超过20-25万。 这表明,如果没有吸烟因素,体重的影响就不那么明显。
吸烟者中有两条线清晰可见:一组吸烟者平均花费约20千人-体重正常或超重的人,另一组持续较高-约35-50千人-这些是肥胖吸烟者。
让我们检查孩子的数量是否影响人们的开支。
plt.figure(figsize=(8,5))
sns.barplot(x="children", y="charges", data=df, estimator=lambda x: x.mean())
plt.title("按子女人数划分的平均开支")
plt.xlabel("儿童人数")
plt.ylabel("平均开支")
plt.show()
儿童人数对医疗费用几乎没有影响。 唯一的事情是当有2-3个孩子时,费用略高于平均水平
让我们来看看标志的相关性的全貌,画一张热图,如下图所示。
sns.heatmap(df_encode.corr(method='spearman'), cmap="coolwarm", annot=False)
如果您通过标志查看相关性,您还可以看到bmi与居住区域,特别是"西南"区域之间存在轻微关系。 也就是说,它们具有更明显的线性关系。
模型训练
我们将在培训期间评估的模型列表
- 线性回归
- 随机森林
- CatBoost回归器
班轮回归(套索)
让我们创建一个表,我们将记录模型训练的结果。
Result_model = pd.DataFrame(columns=["Model", "MAE", "RMSE", "R2"])
对于线性模型,正确使用类别变量非常重要,这就是为什么我们使用一个热编码对它们进行编码的原因。
在下面的代码中,y是我们的目标变量,成本,X是模型将被训练的特征。 整体数据集分为测试集和训练集,这样在训练模型之后,我们就可以对其尚未遇到的数据评估其泛化能力。
我们将用于评估训练模型的指标:
- MAE是平均绝对误差,它将显示模型平均错误的程度。
- RMSE是均方根误差,大的误差受到更严重的惩罚
- R2是确定系数,它显示了模型如何很好地解释"数据
X = df_encode.drop("charges", axis=1)
y = df_encode["charges"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
第一个模型将是套索。 下面的代码提供了模型认为信息较少的训练、度量计算和输出。 然而,即使这样的迹象可以有显着的贡献。
lasso = Lasso(alpha=0.5)
lasso.fit(X_train, y_train)
y_pred_lasso = lasso.predict(X_test)
mae_lasso = mean_absolute_error(y_test, y_pred_lasso)
rmse_lasso = np.sqrt(mean_squared_error(y_test, y_pred_lasso))
r2_lasso = r2_score(y_test, y_pred_lasso)
print("Lasso Regression")
print("MAE:", mae_lasso)
print("RMSE:", rmse_lasso)
print("R²:", r2_lasso)
coef_lasso = pd.DataFrame({
"Feature": X.columns,
"Coefficient": lasso.coef_
}).sort_values(by="Coefficient", ascending=False)
print("套索系数:")
print(coef_lasso)
向表中添加度量值
row = pd.DataFrame({
"Model": ["Lasso"],
"MAE": [mae_lasso],
"RMSE": [rmse_lasso],
"R2": [r2_lasso]
})
Result_model = pd.concat([Result_model, row])
随机森林
下一个模型将是随机森林。 原理是一样的,我们训练,我们测试,我们看标志的重要性
rf = RandomForestRegressor(
n_estimators=100,
max_depth=4,
min_samples_split = 20,
min_samples_leaf = 20
)
rf.fit(X_train, y_train)
y_pred_rf = rf.predict(X_test)
mae_rf = mean_absolute_error(y_test, y_pred_rf)
rmse_rf = np.sqrt(mean_squared_error(y_test, y_pred_rf))
r2_rf = r2_score(y_test, y_pred_rf)
print("Random Forest")
print("MAE:", mae_rf)
print("RMSE:", rmse_rf)
print("R²:", r2_rf)
importances = pd.DataFrame({
"Feature": X.columns,
"Importance": rf.feature_importances_
}).sort_values(by="Importance", ascending=False)
print("\迹象的重要性:")
print(importances)
向表中添加指标
row = pd.DataFrame({
"Model": ["Random Forest"],
"MAE": [mae_rf],
"RMSE": [rmse_rf],
"R2": [r2_rf]
})
Result_model = pd.concat([Result_model, row])
可以看出,随机森林模型的度量比线性模型的度量要好得多。
CatBoost回归器
下一个模型是CatBosot包中的梯度提升模型。 让我们通过与上面的模型相同的管道。
cat_features = ["smoker", "sex", "region"]
feature_cols = [c for c in df.columns if c not in ["charges"]]
cat = CatBoostRegressor(
iterations=1000,
depth=6,
learning_rate=0.01,
verbose=100, random_state=42
)
X = df[feature_cols]
y = df["charges"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
cat.fit(X_train, y_train, cat_features=cat_features, eval_set=(X_test, y_test), verbose=100)
y_pred_cat = cat.predict(X_test)
mae_cat1 = mean_absolute_error(y_test, y_pred_cat)
rmse_cat1 = np.sqrt(mean_squared_error(y_test, y_pred_cat))
r2_cat1 = r2_score(y_test, y_pred_cat)
feature_importance = cat.get_feature_importance(prettified=True)
print(feature_importance)
print("CatBoost")
print("MAE:", mae_cat1)
print("RMSE:", rmse_cat1)
print("R²:", r2_cat1)
向表中添加指标
row = pd.DataFrame({
"Model": ["CatBoostRegressor 1ver"],
"MAE": [mae_cat1],
"RMSE": [rmse_cat1],
"R2": [r2_cat1]
})
Result_model = pd.concat([Result_model, row])
接下来,我们将尝试为梯度提升模型选择更多最优超参数。 在这里,我上面描述的三个指标将同时考虑在内。
我们将通过随机选择模型使用的参数及其范围来搜索超参数。
scoring = {
"mae": "neg_mean_absolute_error",
"rmse": "neg_root_mean_squared_error",
"r2": "r2",
}
cat_base = CatBoostRegressor(
verbose=False,
random_state=42,
task_type="GPU",
devices="0"
)
param_distributions = {
"iterations": randint(300, 1000),
"depth": randint(4, 8),
"learning_rate": loguniform(1e-3, 3e-1),
"l2_leaf_reg": loguniform(1e-2, 1e2),
"bagging_temperature": uniform(0.0, 1.0),
"random_strength": loguniform(1e-3, 10),
"grow_policy": ["SymmetricTree", "Depthwise"],
"border_count": randint(32, 128),
"leaf_estimation_iterations": randint(1, 3)
}
rs = RandomizedSearchCV(
estimator=cat_base,
param_distributions=param_distributions,
n_iter=20,
scoring=scoring,
cv=KFold(n_splits=5, shuffle=True, random_state=42),
n_jobs=1,
random_state=42,
refit=False
)
rs.fit(X_train, y_train, cat_features=cat_features)
接下来,我们将在所有指标中找到最佳结果。 在我们的例子中,确定系数通常显示所有指标的最佳数字。
cv = rs.cv_results_
best_idx = np.argmax(cv["mean_test_r2"])
best_params = cv["params"][best_idx]
print("R2的最佳配置:", best_params)
使用获得的超参数,我们将训练模型
best_cat = CatBoostRegressor(
verbose=False, random_state=42, task_type="GPU", devices="0", **best_params
)
best_cat.fit(X_train, y_train, cat_features=cat_features)
y_pred = best_cat.predict(X_test)
mae = mean_absolute_error(y_test, y_pred)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
r2 = r2_score(y_test, y_pred)
print("MAE:", mae)
print("RMSE:", rmse)
print("R²:", r2)
fi = best_cat.get_feature_importance(prettified=True)
print(fi)
我们还将计算的指标添加到表中。
row = pd.DataFrame({
"Model": ["CatBoostRegressor 2ver"],
"MAE": [mae],
"RMSE": [rmse],
"R2": [r2]
})
Result_model = pd.concat([Result_model, row])
让我们通过从现有功能中创建新功能来进行功能工程。 在我们的例子中,我们将年龄分为几十年,将有孩子的人分成小组,也将bmi指数分为类(即按间隔)。
df = pd.read_csv("insurance.csv")
df["age_decade"] = (df["age"] // 10).astype(int).astype("category")
df["children_bucket"] = pd.cut(df["children"], [-1,0,2,99], labels=["0","1-2","3+"])
df["obesity_class"] = pd.cut(df["bmi"],
[0,18.5,25,30,35,40,1e3], labels=["Under","Normal","Over","Ob1","Ob2", "Ob3"])
之后,我们将进行相同的管道来训练基本模型。
cat_features = ["smoker", "sex", "region", "obesity_class", "age_decade", "children_bucket"]
feature_cols = [c for c in df.columns if c not in ["charges"]]
cat = CatBoostRegressor(
iterations=2000,
depth=6,
learning_rate=0.01,
verbose=100, random_state=42
)
X = df[feature_cols]
y = df["charges"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
cat.fit(X_train, y_train, cat_features=cat_features, eval_set=(X_test, y_test), verbose=100)
y_pred_cat = cat.predict(X_test)
mae_cat = mean_absolute_error(y_test, y_pred_cat)
rmse_cat = np.sqrt(mean_squared_error(y_test, y_pred_cat))
r2_cat = r2_score(y_test, y_pred_cat)
feature_importance = cat.get_feature_importance(prettified=True)
print(feature_importance)
print("CatBoost")
print("MAE:", mae_cat)
print("RMSE:", rmse_cat)
print("R²:", r2_cat)
好吧,让我们在表中写下指标。
row = pd.DataFrame({
"Model": ["CatBoostRegressor 3ver"],
"MAE": [mae_cat],
"RMSE": [rmse_cat],
"R2": [r2_cat]
})
Result_model = pd.concat([Result_model, row])
接下来,让我们检查通过随机搜索找到的具有最佳参数的模型如何在具有新特征的数据集上表现。
cat_features = ["smoker", "sex", "region", "obesity_class", "age_decade", "children_bucket"]
feature_cols = [c for c in df.columns if c not in ["charges"]]
best_cat = CatBoostRegressor(
verbose=False, random_state=42, task_type="GPU", devices="0", **best_params
)
X = df[feature_cols]
y = df["charges"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.15, random_state=42)
cat.fit(X_train, y_train, cat_features=cat_features, eval_set=(X_test, y_test), verbose=100)
y_pred_cat = cat.predict(X_test)
mae_cat = mean_absolute_error(y_test, y_pred_cat)
rmse_cat = np.sqrt(mean_squared_error(y_test, y_pred_cat))
r2_cat = r2_score(y_test, y_pred_cat)
feature_importance = cat.get_feature_importance(prettified=True)
print(feature_importance)
print("CatBoost")
print("MAE:", mae_cat)
print("RMSE:", rmse_cat)
print("R²:", r2_cat)
像往常一样,我们将结果记录在一个表中。
row = pd.DataFrame({
"Model": ["CatBoostRegressor 4ver"],
"MAE": [mae_cat],
"RMSE": [rmse_cat],
"R2": [r2_cat]
})
Result_model = pd.concat([Result_model, row])
让我们进行一个实验。 我们会找到异常值并删除它们。 让我们检查梯度助推器模型的行为。
在这种情况下,我们正在删除数据,这些数据可能会极大地扭曲模型的图片并破坏指标。
我们删除以下数据:
0.05是分位数—低于5%观测值的值。
0.95是分位数值,高于该分位数值发现5%的观测值。
df = pd.read_csv("insurance.csv")
numeric_cols = df.select_dtypes(include=[np.number]).columns
low = df[numeric_cols].quantile(0.05)
high = df[numeric_cols].quantile(0.95)
df_no_outliers = df[~((df[numeric_cols] < low) | (df[numeric_cols] > high)).any(axis=1)]
我们将显示有关数据集的信息
df_no_outliers.info()
可以看出,大约有300份数据被删除。 在这种情况下,我们失去了所有信息的20%,这是不推荐的,但我们看看这20%如何影响模型的最终预测。
让我们构建在上面段落中构建的箱线图,并估计数据的分布。
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
sns.boxplot(y="region", x="charges", data=df_no_outliers, ax=axes[0])
axes[0].set_title("按地区划分的开支")
sns.boxplot(y="sex", x="charges", data=df_no_outliers, ax=axes[1])
axes[1].set_title("按性别划分的开支")
sns.boxplot(y="smoker", x="charges", data=df_no_outliers, ax=axes[2])
axes[2].set_title("吸烟费用")
plt.tight_layout()
plt.show()
可见排放量略少
接下来,我们将在裁剪的数据集上训练梯度提升模型,并检查度量
cat_features = ["smoker", "sex", "region"]
feature_cols = [c for c in df.columns if c not in ["charges"]]
cat = CatBoostRegressor(
iterations=2700,
depth=9,
learning_rate=0.002,
verbose=100, random_state=42
)
X = df_no_outliers[feature_cols]
y = df_no_outliers["charges"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.15, random_state=42)
cat.fit(X_train, y_train, cat_features=cat_features, eval_set=(X_test, y_test), verbose=100)
y_pred_cat = cat.predict(X_test)
mae_cat = mean_absolute_error(y_test, y_pred_cat)
rmse_cat = np.sqrt(mean_squared_error(y_test, y_pred_cat))
r2_cat = r2_score(y_test, y_pred_cat)
feature_importance = cat.get_feature_importance(prettified=True)
print(feature_importance)
print("CatBoost")
print("MAE:", mae_cat)
print("RMSE:", rmse_cat)
print("R²:", r2_cat)
让我们把所有的东西都放在平板电脑上。
row = pd.DataFrame({
"Model": ["CatBoostRegressor 5ver"],
"MAE": [mae_cat],
"RMSE": [rmse_cat],
"R2": [r2_cat]
})
Result_model = pd.concat([Result_model, row], ignore_index=True)
让我们评估结果
Result_model
从下表可以看出,R2指标的最佳值由随机森林和梯度提升模型(版本5)显示。 但是,应该记住,梯度提升模型是在变换数据上训练的,其中删除了潜在的异常值(即使这样的客户端实际上可能发生在样本中)。 与此同时,随机森林模型在初始数据集上显示了更高的R2值,但其RMSE和MAE指标更差。 这表明在初始数据中存在成本显着高于平均水平的观测值。 因此,平均价值约为20千单位,有些情况下费用约为60千单位。 这样的极值增加了随机森林的绝对度量中的误差。 相比之下,没有异常值训练的梯度提升模型显示出较低的MAE和RMSE误差。
因此,在所有训练好的模型中,我们将认为指标最低的模型是最好的。
在我们的例子中,这是梯度提升版本5。
哪些因素对目标影响最大?
让我们用2辐条来评估标志的重要性:
- 基于EDA的重要性
- 在模型的帮助下获得的重要性
基于EDA的重要性
让我们分别考虑所有的迹象
吸烟(吸烟者)
最显着的因素。 吸烟与费用之间的相关系数为0.66,这表明强烈的关系。
图表显示,吸烟者的支出明显高于非吸烟者。
散点图还显示,即使年龄和BMI相同,吸烟者的成本也会显着提高。
吸烟极大地增加了健康风险(心脏病,肺病,癌症),从而导致更高的成本。
年龄(age)
年龄在影响力方面排在第二位(相关性0.53)。
图表显示,一个人年龄越大,成本就越高。
这对于吸烟者来说尤其明显:随着年龄的增长,他们的费用增加,看起来有点像指数依赖。
原因是,随着年龄的增长,患慢性病和寻求医疗帮助的可能性增加。
儿童的存在
相关性较弱(0.13),但仍然为正。
有2-3个孩子的家庭平均支出高于没有孩子或有一个孩子的家庭。
然而,对于5个孩子,平均费用减少—这可能是由于样本的特征。
原因是生孩子会增加健康成本
体重指数(BMI)
与费用的相关性较弱(0.11),但与其他因素相结合,BMI发挥作用。
图表显示,肥胖的人有更高的费用,特别是如果他们吸烟。
与此同时,不吸烟的肥胖并不总是导致成本急剧增加。
原因是肥胖与糖尿病和心血管疾病的风险有关,但这些影响因其他风险因素而增强。
性别与地区(sex,region)
性别或地区没有明显的影响。
相关性接近于零。
男性和女性以及不同地区的费用分配几乎相同。
原因是保险费率和药品不依赖于该样本中的居住地区或性别。
模型的帮助下获得的重要性
如果我们在训练模型后查看哪些特征更多,哪些权重更小,我们将看到类似的重要性图片。
因此,例如,对于随机森林模型,训练后特征的重要性系数:
-
烟鬼0.700647 -
体重指数0.175183 -
年龄0.115571
可以看出,在EDA期间也发现这些非常标志具有很高的重要性。
例如,对于两个梯度增强模型,这些特征是重要的,只是具有不同的系数。:
-
吸烟者 -
体重指数 -
年龄
因此,可以明确得出结论,最显着的迹象是吸烟因素,bmi指数和年龄。
结论
在该示例中,执行了探索性数据分析,训练了回归模型,并确定了影响目标变量的最显着特征。
.png)
.png)
.png)
.png)
.png)
.png)
.png)